package cn.jussi.service;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Date;
import java.util.List;
import java.util.UUID;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.FileUploadBase;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.apache.commons.fileupload.servlet.ServletFileUpload;

import cn.jussi.dao.DaoFactory;
import cn.jussi.dao.UploadFileDao;
import cn.jussi.model.UploadFile;
import cn.jussi.utils.UploadFilePropertiesUtil;

public class UploadFileServiceImpl implements UploadFileService {

	UploadFileDao uploadFileDao = DaoFactory.getUploadFileDao();
	
	private String saveDir = UploadFilePropertiesUtil.getInstance().getProperty("savePath");
	private String tempDir = UploadFilePropertiesUtil.getInstance().getProperty("tempPath");
	private String sizeThreshold = UploadFilePropertiesUtil.getInstance().getProperty("sizeThreshold");
	private String fileSizeMax = UploadFilePropertiesUtil.getInstance().getProperty("fileSizeMax");
	private String sizeMax = UploadFilePropertiesUtil.getInstance().getProperty("sizeMax");
	private String fileEx = UploadFilePropertiesUtil.getInstance().getProperty("fileEx");
	
	@Override
	public void addFileInfo(UploadFile uploadFile) {
		//把上传上来的文件保存到数据库之前，我们要先把文件保存到服务器上的savePath
		uploadFileDao.addFileInfo(uploadFile);
	}

	@Override
	public UploadFile getUploadFilesById(int id) {
		return uploadFileDao.get(id);
	}	
	
	@Override
	public List<UploadFile> getUploadFilesInfo() {
		return uploadFileDao.getUploadFilesInfo();
	}

	@Override
	public void deleteUploadFileInfo(int id) {
		UploadFile uFile = this.getUploadFilesById(id);
		//先把数据库里的信息删除
		uploadFileDao.deleteUploadFileInfo(id);
		//还得把服务器磁盘上的文件删除
		deleteFile(uFile.getSavePath()+"\\"+uFile.getSaveName());
	}

	@Override
	public void saveFile(HttpServletRequest req, HttpServletResponse resp) {	
		//1.获取和创建保存文件的最终目录和临时目录
		String savePath = req.getSession().getServletContext().getRealPath(this.saveDir);	//保存文件的服务器上的绝对地址
		String tempPath = req.getSession().getServletContext().getRealPath(this.tempDir);	//临时目录
		File tempFile = new File(tempPath);
		if(!tempFile.exists()) {
			tempFile.mkdirs();	//如果临时目录不存在的话，用代码创建一个目录
		}	
		//2.创建一个DiskFileItemFactory工厂
		DiskFileItemFactory factory = new DiskFileItemFactory();
		factory.setSizeThreshold(Integer.parseInt(this.sizeThreshold));	//100KB, 上传的文件， <100KB，放在内存里， >100KB，放在tempPath
		factory.setRepository(tempFile);	//设置上传文件的临时目录
		//3.创建一个文件上传解析器
		ServletFileUpload servletFileUpload = new ServletFileUpload(factory);	//得到文件的解析器
		servletFileUpload.setFileSizeMax(Integer.parseInt(this.fileSizeMax));	//限制上传的单个文件的大小在20MB之内
		servletFileUpload.setHeaderEncoding("UTF-8");	//防止上传的中文信息乱码
		servletFileUpload.setSizeMax(Integer.parseInt(this.sizeMax)); //限制多个文件同时上传的时的最大容量
		//4.判断提交上来的数据是否是上传表单的数据，是不是Multipart编码方式
		if(!ServletFileUpload.isMultipartContent(req)) {
			throw new RuntimeException("上传文件的Form的编码方式不正确！");
		}
		//5.使用ServletFileUpload解析器解析上传数据
		String desc = "";
		String fileName = "";
		String fileType = "";
		long fileSize = 0;
		String fileEx1 = "";
		String saveFileName = "";
		String realSavePath = "";
		InputStream in = null;
		FileOutputStream out = null;
		try {
			List<FileItem> fileList = servletFileUpload.parseRequest(req);
			if(fileList != null && fileList.size()>0) {
				for(FileItem fileItem: fileList) {
					//6.判断是普通域还是文件域
					if(fileItem.isFormField()) {
						//7.将普通表单域的键值对显示出来
						desc = fileItem.getString("UTF-8");
						//每一次为desc重新赋值，代表着一个文件已经上传，完成了一次文件上传的操作
						if(!"".equals(fileName)) {
							UploadFile uf = new UploadFile();
							uf.setDesc(desc);
							uf.setFileSize(fileSize+"");
							uf.setFileType(fileType);
							uf.setOldFileName(fileName);
							uf.setSavePath(realSavePath);
							uf.setSaveName(saveFileName);
							uf.setSaveTime(new Date());
							addFileInfo(uf);
						}
					}else {
						//通过FileItem，拿到上传上来的文件的各种信息 和 文件的内容
						fileName = fileItem.getName(); //拿到文件名字 （xxx.doc xxx.txt）
						if(fileName == null || fileName.trim().equals("")) {
							continue;
						}
						//注意：ie拿到的fileName是带绝对路径（D:\abc\xxx.doc） 火狐拿到的是文件名（xxx.doc）
						fileName = fileName.substring(fileName.lastIndexOf("\\")+1);
						fileType = fileItem.getContentType(); //拿到文件的类型 image/jpg
						fileSize = fileItem.getSize(); //拿到文件的总大小
						//8.验证后缀的合法性
						fileEx1 = fileName.substring(fileName.lastIndexOf(".")+1);
						if(!(this.fileEx.indexOf(fileEx1) == -1)) {
							throw new RuntimeException("禁止上传该文件类型！");
						}
						//9.将文件流写入保存的目录中（生成新的文件名，避免重名覆盖）
						saveFileName = makeFileName(fileName);
						realSavePath = makePath(saveFileName, savePath);
						//创建一个输出流
						in = fileItem.getInputStream();
						out = new FileOutputStream(realSavePath + "\\" + saveFileName);
						//建立缓存区
						byte[] buffer = new byte[1024];
						int len = 0;
						while((len = in.read(buffer)) > 0) {
							out.write(buffer, 0, len);
						}
						in.close();
						out.close();
					}
				}
			}
			//删除临时目录下的临时文件
			File temps = new File(tempPath);
			for(File file: temps.listFiles()) {
				file.delete();
			}
		} catch (FileUploadBase.SizeLimitExceededException e) {
			throw new RuntimeException("上传文件总大小超出了限制：" + Integer.parseInt(this.sizeMax)/(1024*1024) + "MB!");
		} catch (FileUploadBase.FileSizeLimitExceededException e) {
			throw new RuntimeException("上传单个文件的大小超出了限制：" + Integer.parseInt(this.fileSizeMax)/(1024*1024) + "MB!");
		} catch (Exception e) {
			throw new RuntimeException(e.getMessage());
		} finally {
			if(in != null) {
				try {
					in.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
			if(out != null){
				try {
					out.close();
				} catch (IOException e) {
					e.printStackTrace();
				}	
			}
		}
	}

	public void deleteFile(String savePath) {
		File file = new File(savePath);
		if(file.isFile()) {
			file.delete();
		}
	}
	
	private String makePath(String saveFileName, String savePath) {
		//拿到文件名在内存中的地址，hashCode值
		int hashCode = saveFileName.hashCode();
		int dir1 = hashCode & 0xf;	//dir1的值，&运算的结果范围  0-15
		int dir2 = (hashCode>>4) & 0xf;	//得到的结果范围还是 0-15
		//用 dir1 和 dir2 构造我的新的存储文件的目录
		String dir = savePath + "\\" + dir1 + "\\" + dir2;
		File file = new File(dir);
		if(!file.exists()) {
			file.mkdirs();
		}
		return dir;
	}

	private String makeFileName(String fileName) {
		return UUID.randomUUID().toString() + "_" + fileName;
	}
}
